package com.guli.mall.wms.mq;
/*
 * @Author 罗俊
 * @date 2020/11/25 - 5:51 下午
 *
 */

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.guli.common.constant.OrderConstant;
import com.guli.common.constant.WareConstant;
import com.guli.common.to.LockStockInfoTo;
import com.guli.common.to.OrderTo;
import com.guli.common.to.StockWareTo;
import com.guli.mall.wms.entity.WareOrderTaskDetailEntity;
import com.guli.mall.wms.feign.OrderFeignClient;
import com.guli.mall.wms.service.WareOrderTaskDetailService;
import com.guli.mall.wms.service.WareOrderTaskService;
import com.guli.mall.wms.service.WareSkuService;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.List;

/**
 * 解锁库存
 */
@RabbitListener(queues = WareConstant.STOCK_RELEASE_QUEUE)
@Component
@Slf4j
public class UnLockStock {

    @Resource
    OrderFeignClient orderFeignClient;

    @Resource
    WareSkuService wareSkuService;

    @Resource
    WareOrderTaskDetailService wareOrderTaskDetailService;

    public static final TypeReference<OrderTo> ORDER_TYPE_REFERENCE = new TypeReference<OrderTo>() {
    };

    @RabbitHandler
    public void judgeLockStock(StockWareTo stockWareTo, Channel channel, Message message) {

        // 判断该消息是不是重新进入队列的，当前消息是不是第二次以及以后派发过来的，但是不知道上一次是否处理成功
        Boolean redelivered = message.getMessageProperties().getRedelivered();


        Long orderId = stockWareTo.getOrderId();

        List<LockStockInfoTo> lockStockInfoTos = stockWareTo.getLockStockInfoTos();

        for (LockStockInfoTo lockStockInfoTo : lockStockInfoTos) {

            WareOrderTaskDetailEntity byId = wareOrderTaskDetailService.getById(lockStockInfoTo.getWareOrderTaskDetailEntityId());

            if (ObjectUtils.isEmpty(byId)) {

                log.info("根据 锁库存记录id = {}去查询 库存记录，发现没有对应的库存记录，说明是锁库存失败了，整个库存操作都回滚了，所以锁定失败，无需解锁库存", lockStockInfoTo.getWareOrderTaskDetailEntityId());
                // 只要有一个库存记录不存在，说明就是全体回滚，剩下的库存记录id都不用判断了
                confirmDealingMessage(channel, message);
                return;
            }

            // 判断一个就够了
            break;
        }

        // 到这里证明：库存锁定成功了，其他服务是否成功，状态未知

        //TODO 因为 order 服务有登陆拦截器，所要要给 feign 添加拦截器，配置登陆信息
        //这里让目标微服务直接路径匹配，放心本此调用
        Object order = null;
        try {

            order = orderFeignClient.getOrderByOrderId(orderId).get("order");

        } catch (Exception e) {

            log.error("获取订单id = {}的订单信息失败，将消息重新放回队列，让其他消费者消费", orderId);

            try {
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }

            return;
        }

        if (ObjectUtils.isEmpty(order)) {
            // 订单不存在
            // 执行解锁库存
            log.warn("订单id = {}已经不存在，即将解锁库存{}", orderId, lockStockInfoTos);
            wareSkuService.unlockStock(lockStockInfoTos);

            confirmDealingMessage(channel, message);
            return;
        }


        OrderTo orderVo = JSON.parseObject(JSON.toJSONString(order), ORDER_TYPE_REFERENCE);

        if (ObjectUtils.isEmpty(orderVo.getStatus())) {

            log.warn("订单id = {}，的订单状态为【{}】，没有状态，即将解锁库存", orderVo.getId(), orderVo.getStatus());

            wareSkuService.unlockStock(lockStockInfoTos);

            confirmDealingMessage(channel, message);

            return;
        }

        if (orderVo.getStatus().getCode().equals(OrderConstant.Status.WAITING_FOR_PAYMENT.getCode()) ||
                orderVo.getStatus().getCode().equals(OrderConstant.Status.CLOSED.getCode()) ||
                orderVo.getStatus().getCode().equals(OrderConstant.Status.INVALID.getCode())) {
            // 订单尚未付款
            // 执行解锁库存
            log.warn("订单id = {}的状态为【{}】，即将解锁库存，{}", orderId, orderVo.getStatus().getMessage(), lockStockInfoTos);

            wareSkuService.unlockStock(lockStockInfoTos);

            confirmDealingMessage(channel, message);

            return;

        }


        log.info("订单id = {}支付成功，现在修改锁定订单表 wms_ware_order_task_detail 的锁定状态", orderId);
        wareOrderTaskDetailService.updateStockLockStatus(lockStockInfoTos, WareConstant.LockStatus.DEDUCTION);


        confirmDealingMessage(channel, message);

    }

    /**
     * 防止订单服务卡顿，导致订单状态的消息，订单服务一直未能及时处理
     * 而库存消息先被处理，此时调用查询订单状态，订单为新建状态，库存误以为订单失效（但其实写入到数据库的的订单消息还在路上）
     * 库存就把锁库存的记录给解锁了，最终导致已经付款，但是库存却没有被扣除的的情况
     *
     * @param orderTo
     * @param channel
     * @param message
     */
    @RabbitHandler
    public void releaseLockAfterSystemCancelOrder(OrderTo orderTo, Channel channel, Message message) {

        log.info("库存收到订单服务的修改:{}", orderTo);


        try {

            if (ObjectUtils.isEmpty(orderTo) || ObjectUtils.isEmpty(orderTo.getId())) {
                log.warn("订单{}的内容为null，现在直接消费该消息，而不操作库存", orderTo);
            } else {

                // 注意一定要查一下锁定库存的最新状态，防止重复扣除库存（已经在下面的方法中实现了）
                wareSkuService.unlockStockByOrderId(orderTo.getId());
            }


            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            e.printStackTrace();
            try {
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }


    /**
     * 确认消息已被处理
     * 只要解锁库存消息失败，一定要告诉MQ，解锁失败，让消息重新入队列
     *
     * @param channel
     * @param message
     */
    private void confirmDealingMessage(Channel channel, Message message) {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            channel.basicAck(deliveryTag, false);
            log.info("已经确认库存解锁消息 ，deliveryTag = {}", deliveryTag);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}
